unit uVorbisFile;

interface

const
{$IFDEF WIN32}
  VorbisFileLib = 'vorbisfile.dll';
{$ELSE}
  VorbisFileLib = 'libvorbisfile.so';
{$ENDIF}

type
  size_t         = cardinal;
  int            = integer;
  long           = integer;
  unsigned_long  = cardinal;
  float          = single;
  signed_char    = shortint;

  ogg_int64_t  = int64;
  ogg_int32_t  = longint;
  ogg_uint32_t = longword;
  ogg_int16_t  = -32768..32767;

  p_int  = ^int;

  p_pchar_array = ^t_pchar_array;
  t_pchar_array = packed array[0..maxint div SizeOf(PChar)-1] of PChar;

  p_int_array = ^t_int_array;
  t_int_array = packed array[0..maxint div SizeOf(int)-1] of int;

  p_float_array = ^t_float_array;
  t_float_array = packed array[0..maxint div SizeOf(float)-1] of float;

  p_p_float_p_float_array = ^p_float_p_float_array;
  p_float_p_float_array = ^t_float_p_float_array;
  t_float_p_float_array = packed array[0..maxint div SizeOf(p_float_array)-1] of p_float_array;

  p_signed_char_array = ^t_signed_char_array;
  t_signed_char_array = packed array[0..maxint div SizeOf(signed_char)-1] of signed_char;
  
  p_oggpack_buffer = ^oggpack_buffer;
    oggpack_buffer = record
      endbyte: long;
      endbit: int;
      buffer: PChar;
      ptr: PChar;
      storage: long;
    end;

  p_ogg_page = ^ogg_page;
    ogg_page = record
      header: Pointer;
      header_len: long;
      body: Pointer;
      body_len: long;
    end;

  p_ogg_stream_state = ^ogg_stream_state;
    ogg_stream_state = record
      body_data: PChar;
      body_storage: long;
      body_fill: long;
      body_returned: long;

      lacing_vals: p_int_array;
      granule_vals: Pointer;

      lacing_storage: long;
      lacing_fill: long;
      lacing_packet: long;
      lacing_returned: long;

      header: array[0..282] of Char;
      header_fill: int;
      e_o_s: int;
      b_o_s: int;

      serialno: long;
      pageno: long;
      packetno: ogg_int64_t;
      
      granulepos: ogg_int64_t;
    end;

  p_ogg_packet = ^ogg_packet;
    ogg_packet = record
      packet: PChar;
      bytes: long;
      b_o_s: long;
      e_o_s: long;

      granulepos: ogg_int64_t;

      packetno: ogg_int64_t;
    end;

  p_ogg_sync_state = ^ogg_sync_state;
    ogg_sync_state = record
      data: PChar;
      storage: int;
      fill: int;
      returned: int;

      unsynced: int;
      headerbytes: int;
      bodybytes: int;
    end;

  p_vorbis_info = ^vorbis_info;
    vorbis_info = record
      version: int;
      channels: int;
      rate: long;

      bitrate_upper: long;
      bitrate_nominal: long;
      bitrate_lower: long;
      bitrate_window: long;
      codec_setup: Pointer;
    end;

  p_vorbis_dsp_state = ^vorbis_dsp_state;
    vorbis_dsp_state = record
      analysisp: int;
      vi: p_vorbis_info;
      pcm: p_float_p_float_array;
      pcmret: p_float_p_float_array;
      pcm_storage: int;
      pcm_current: int;
      pcm_returned: int;
      preextrapolate: int;
      eofflag: int;
      lW: long;
      W: long;
      nW: long;
      centerW: long;
      granulepos: ogg_int64_t;
      sequence: ogg_int64_t;
      glue_bits: ogg_int64_t;
      time_bits: ogg_int64_t;
      floor_bits: ogg_int64_t;
      res_bits: ogg_int64_t;
      backend_state: Pointer;
    end;

  p_alloc_chain = ^alloc_chain;
    alloc_chain = record
      ptr: Pointer;
      next: p_alloc_chain;
    end;

  p_vorbis_block = ^vorbis_block;
    vorbis_block = record
      pcm: p_float_p_float_array;
      opb: oggpack_buffer;
      lW: long;
      W: long;
      nW: long;
      pcmend: int;
      mode: int;

      eofflag: int;
      granulepos: ogg_int64_t;
      sequence: ogg_int64_t;
      vd: p_vorbis_dsp_state;

      localstore: Pointer;
      localtop: long;
      localalloc: long;
      totaluse: long;
      reap: p_alloc_chain;

      glue_bits: long;
      time_bits: long;
      floor_bits: long;
      res_bits: long;

      internal: Pointer;
          end;

  p_vorbis_comment = ^vorbis_comment;
    vorbis_comment = record
      user_comments: p_pchar_array;
      comment_lengths: p_int_array;
      comments: int;
      vendor: PChar;
    end;

  read_func_t  = function(var ptr; size: size_t; nmemb: size_t; const datasource): size_t; cdecl;
  seek_func_t  = function(const datasource; offset: ogg_int64_t; whence: int): int; cdecl;
  close_func_t = function(const datasource): int; cdecl;
  tell_func_t  = function(const datasource): long; cdecl;

  p_ov_callbacks = ^ov_callbacks;
    ov_callbacks = record
      read_func:  read_func_t;
      seek_func:  seek_func_t;
      close_func: close_func_t;
      tell_func:  tell_func_t;
    end;

  POggVorbis_File = ^OggVorbis_File;
   OggVorbis_File = record
      datasource: Pointer;
      seekable: int;
      offset: ogg_int64_t;
      end_v: ogg_int64_t;
      oy: ogg_sync_state;

      links: int;
      offsets: Pointer;
      dataoffsets: Pointer;
      serialnos: Pointer;
      pcmlengths: Pointer;
      vi: p_vorbis_info;
      vc: p_vorbis_comment;

      pcm_offset: ogg_int64_t;
      ready_state: int;
      current_serialno: long;
      current_link: int;

      bittrack: double;
      samptrack: double;

      os: ogg_stream_state;
      vd: vorbis_dsp_state;
      vb: vorbis_block;

      callbacks: ov_callbacks;
   end;

var
  ov_clear: function(var vf: OggVorbis_File): int; cdecl;
  ov_open_callbacks: function(const datasource; var vf: OggVorbis_File; initial: PChar; ibytes: long; callbacks: ov_callbacks): int; cdecl;
  ov_info: function(var vf: OggVorbis_File; link: int): p_vorbis_info; cdecl;
  ov_read: function(var vf: OggVorbis_File; const buffer; length: int; bigendianp: int; word: int; sgned: int; bitstream: p_int): int; cdecl;

function InitVorbisFile(libName: String = VorbisFileLib): Boolean;

function ops_read_func(var ptr; size, nmemb: size_t; const datasource): size_t; cdecl;
function ops_seek_func(const datasource; offset: ogg_int64_t; whence: int): int; cdecl;
function ops_close_func(const datasource): int; cdecl;
function ops_tell_func(const datasource): long; cdecl;

var
  ops_callbacks: ov_callbacks;

implementation

uses
  Classes, uLib;

var
  lib: Cardinal = 0;

function InitVorbisFile(libName: String = VorbisFileLib): Boolean;
begin
  Result := False;

  if lib <> 0 then
  begin
    Result := True;
    Exit;
  end;

  lib := LibOpen(libName);

  if lib <> 0 then
  begin
    ov_clear := LibProc(lib, 'ov_clear');
    ov_open_callbacks := LibProc(lib, 'ov_open_callbacks');
    ov_info := LibProc(lib, 'ov_info');
    ov_read := LibProc(lib, 'ov_read');

    Result := True;
  end;
end;

const
  SEEK_SET = 0;
  SEEK_CUR = 1;
  SEEK_END = 2;

  EOF = -1;

function ops_read_func(var ptr; size, nmemb: size_t; const datasource): size_t;
begin
  if (size = 0) or (nmemb = 0) then
  begin
    result := 0;
    exit;
  end;

  try
    result := Int64(TStream(datasource).Read(ptr, size * nmemb)) div Int64(size);
  except
    result := 0;
  end;
end;

function ops_seek_func (const datasource; offset: ogg_int64_t; whence: int): int;
begin
  try
    case whence of
      SEEK_CUR: TStream(datasource).Seek(offset, soFromCurrent);
      SEEK_END: TStream(datasource).Seek(offset, soFromEnd);
      SEEK_SET: TStream(datasource).Seek(offset, soFromBeginning);
    end;
    result := 0;
  except
    result := -1;
  end;
end;

function ops_close_func(const datasource): int;
begin
  try
    TStream(datasource).Free;
    result := 0;
  except
    result := EOF;
  end;
end;

function ops_tell_func(const datasource): long;
begin
  try
    result := TStream(datasource).Position;
  except
    result := -1;
  end;
end;

initialization
  ops_callbacks.read_func := ops_read_func;
  ops_callbacks.seek_func := ops_seek_func;
  ops_callbacks.close_func := ops_close_func;
  ops_callbacks.tell_func := ops_tell_func;
end.
